---
title: Combiner des providers
---

Avant de commencer, assurez-vous de lire la section sur [les providers](/docs/concepts2/providers).

### Combiner des providers

Nous avons vu précédemment comment créer un simple provider. Mais la réalité est la suivante, 
dans de nombreuses situations, un provider pourra vouloir lire l'état d'un autre provider.

Pour ce faire, nous pouvons utiliser l'objet [ref] passé au 
callback de notre provider, et utiliser sa méthode [watch].

À titre d'exemple, considérons le provider suivant :

```dart
final cityProvider = Provider((ref) => 'London');
```

Nous pouvons maintenant créer un autre provider qui va consommer notre `cityProvider` :

```dart
final weatherProvider = FutureProvider((ref) async {
  // On utilise `ref.watch` pour écouter un autre provider, 
  // et on lui passe le provider que l'on veut consommer. 
  // Ici : cityProvider
  final city = ref.watch(cityProvider);

  // On peut ensuite utiliser le résultat pour faire quelque chose en 
  // fonction de la valeur de `cityProvider`.
  return fetchWeather(city: city);
});
```

Et voilà. Nous avons créé un provider qui dépend d'un autre provider.

## FAQ

### Que faire si la valeur écoutée change au fil du temps ? Et voilà. Nous avons créé un provider qui dépend d'un autre provider.

Selon le provider que vous écoutez, la valeur obtenue peut changer au fil du temps. 

Par exemple, vous pourriez être à l'écoute d'un [StateNotifierProvider], 
ou le provider écouté a été forcé de se rafraîchir à l'aide de [ProviderContainer.refresh]/[ref.refresh].

En utilisant [watch], Riverpod est capable de détecter que la valeur 
écoutée a changé et réexécutera _automatiquement_ le provider si nécessaire.

Cela peut être utile pour les états (states) calculés.
Par exemple, considérons un [StateNotifierProvider] qui expose une todo-list :

```dart
class TodoList extends StateNotifier<List<Todo>> {
  TodoList(): super(const []);
}

final todoListProvider = StateNotifierProvider((ref) => TodoList());
```
Un cas d'utilisation commun serait de faire en sorte que l'interface utilisateur (UI) filtre la liste des tâches (todos) 
pour n'afficher seulement les tâches terminées/non terminées.

Une façon simple de mettre en place un tel scénario serait de :

- créer [StateProvider], qui présente la méthode de filtrage actuellement sélectionnée :

 ```dart
  enum Filter {
    none,
    completed,
    uncompleted,
  }

  final filterProvider = StateProvider((ref) => Filter.none);
  ```
- créer un provider indépendant qui combine la méthode de filtrage et 
  la todo-list pour exposer la todo-list filtrée :

  ```dart
  final filteredTodoListProvider = Provider<List<Todo>>((ref) {
    final filter = ref.watch(filterProvider);
    final todos = ref.watch(todoListProvider);

    switch (filter) {
      case Filter.none:
        return todos;
      case Filter.completed:
        return todos.where((todo) => todo.completed).toList();
      case Filter.uncompleted:
        return todos.where((todo) => !todo.completed).toList();
    }
  });
  ```

Ensuite, notre interface utilisateur (UI) peut écouter `filteredTodoListProvider` pour écouter la todo-list filtrée. 
En utilisant cette approche, l'interface sera automatiquement mise à jour lorsque le filtre ou 
la liste de tâches changera. ou la todo-list change.

Pour voir cette approche en action, vous pouvez consulter le code source de 
[exemple de Todo List](https://github.com/rrousselGit/riverpod/tree/master/examples/todos).

:::info
Ce comportement n'est pas spécifique à [Provider], et fonctionne avec tous les providers.

Par exemple, vous pouvez combiner [watch] et [FutureProvider] pour implémenter une fonction de recherche 
ou un mode de changement de live-configuration. 

```dart
// Le filtre de recherche actuel
final searchProvider = StateProvider((ref) => '');

/// Des configurations qui peuvent évoluer dans le temps
final configsProvider = StreamProvider<Configuration>(...);

final charactersProvider = FutureProvider<List<Character>>((ref) async {
  final search = ref.watch(searchProvider);
  final configs = await ref.watch(configsProvider.future);
  final response = await dio.get('${configs.host}/characters?search=$search');

  return response.data.map((json) => Character.fromJson(json)).toList();
});
```

Ce code récupère une liste de caractères à partir du service, 
et récupère à nouveau automatiquement la liste, chaque fois que les 
configurations changent ou que la requête de recherche change.
:::

### Peut-on lire un provider sans l'écouter ?

Parfois, on souhaite lire le contenu d'un provider, mais sans recréer la valeur 
exposée lorsque la valeur obtenue change.

Un exemple serait un `Repository`, qui lit depuis un autre provider le jeton de l'utilisateur
pour l'authentification.
Nous pourrions utiliser [watch] et créer un nouveau `Repository` chaque fois que le jeton de l'utilisateur change, 
mais il y a peu ou pas d'utilité à faire cela.

Dans cette situation, nous pouvons utiliser [read], qui est similaire à [watch], mais qui n'obligera pas le provider 
à recréer sa valeur exposée lorsque la valeur obtenue change.

Dans ce cas, une pratique commune est de faire passer `ref.read` à l'objet créé. 
Cet objet sera alors capable de lire les providers quand il le souhaite.

```dart
final userTokenProvider = StateProvider<String>((ref) => null);

final repositoryProvider = Provider((ref) => Repository(ref.read));

class Repository {
  Repository(this.read);

  /// La fonction `ref.read` 
  final Reader read;

  Future<Catalog> fetchCatalog() async {
    String token = read(userTokenProvider);

    final response = await dio.get('/path', queryParameters: {
      'token': token,
    });

    return Catalog.fromJson(response.data);
  }
}
```

:::note
Vous pouvez aussi passer le `ref` au lieu de `ref.read` à votre objet :

```dart
final repositoryProvider = Provider((ref) => Repository(ref));

class Repository {
  Repository(this.ref);

  final Ref ref;
}
```

La seule différence que le passage de `ref.read` apporte est un code 
un peu moins verbeux et la garantie que notre objet n'utilise jamais `ref.watch`.
:::

:::danger **NE PAS** appelez [read] à l'intérieur du provider.

```dart
final myProvider = Provider((ref) {
  // Mauvaise pratique d'appeler `read` ici.
  final value = ref.read(anotherProvider);
});
```

Si vous avez utilisé [read] pour tenter d'éviter les reconstructions non souhaitées de votre objet, reportez-vous à 
[Mon provider fait des mises à jour trop souvent, que puis-je faire ?](#mon-provider-fait-des-mises-à-jour-trop-souvent-que-puis-je-faire-)
Mauvaise pratique pour appeler `read` ici
:::

### Comment tester un objet qui reçoit [read] comme paramètre de son constructeur ?

Si vous utilisez le modèle décrit dans [Peut-on lire un provider sans l'écouter ?](#peut-on-lire-un-provider-sans-lécouter-), 
vous vous demandez peut-être comment écrire des tests pour votre objet.

Dans ce scénario, considérez tester le provider directement au lieu de l'objet brut. 
Vous pouvez le faire en utilisant la classe [ProviderContainer] :

```dart
final repositoryProvider = Provider((ref) => Repository(ref.read));

test('fetches catalog', () async {
  final container = ProviderContainer();
  addTearDown(container.dispose);

  Repository repository = container.read(repositoryProvider);

  await expectLater(
    repository.fetchCatalog(),
    completion(Catalog()),
  );
});
```

### Mon provider fait des mises à jour trop souvent, que puis-je faire ?

Si votre objet est recréé trop souvent, il y a de fortes chances que votre provider soit en train 
d'écouter des objets dont il ne se soucie pas.

Par exemple, vous pouvez écouter un objet `Configuration`, 
mais n'utiliser que sa propriété `host`.
En écoutant l'ensemble de l'objet `Configuration`, si une propriété 
autre que `host` est modifiée, votre provider sera toujours réévalué. Ce qui peut être indésirable.

La solution à ce problème est de créer un provider séparé qui expose _seulement_ ce dont vous 
avez besoin dans `Configuration` (donc `host`).

**ÉVITEZ** l'écoute de l'objet entier :

```dart
final configsProvider = StreamProvider<Configuration>(...);

final productsProvider = FutureProvider<List<Product>>((ref) async {
  // Le ProductsProvider devra récupérer les produits si 
  // un élément de la configuration change.
  final configs = await ref.watch(configsProvider.future);

  return dio.get('${configs.host}/products');
});
```

**PREFEREZ** n'écouter que ce que vous utilisez :

```dart
final configsProvider = StreamProvider<Configuration>(...);

/// Un provider qui n'expose que l'hôte actuel.
final _hostProvider = FutureProvider<String>((ref) async {
  final config = await ref.watch(configsProvider.future);
  return config.host;
});

final productsProvider = FutureProvider<List<Product>>((ref) async {
  // Écoute uniquement le host. Si quelque chose d'autre dans les configurations change, 
  // cela ne réévaluera pas inutilement notre provider.
  final host = await ref.watch(_hostProvider.future);

  return dio.get('$host/products');
});
```

[provider]: https://pub.dev/documentation/riverpod/latest/riverpod/Provider-class.html
[stateprovider]: https://pub.dev/documentation/riverpod/latest/riverpod/StateProvider-class.html
[futureprovider]: https://pub.dev/documentation/riverpod/latest/riverpod/FutureProvider-class.html
[statenotifierprovider]: https://pub.dev/documentation/riverpod/latest/riverpod/StateNotifierProvider-class.html
[ref]: https://pub.dev/documentation/riverpod/latest/riverpod/Ref-class.html
[watch]: https://pub.dev/documentation/riverpod/latest/riverpod/Ref/watch.html
[read]: https://pub.dev/documentation/riverpod/latest/riverpod/Ref/read.html
[providercontainer.refresh]: https://pub.dev/documentation/riverpod/latest/riverpod/ProviderContainer/refresh.html
[ref.refresh]: https://pub.dev/documentation/flutter_riverpod/latest/flutter_riverpod/WidgetRef/refresh.html
[providercontainer]: https://pub.dev/documentation/riverpod/latest/riverpod/ProviderContainer-class.html
